#include <esp_err.h>
#include <esp_log.h>
#include <esp_system.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <freertos/queue.h>

#include "lv_port_disp.h"
#include "lv_port_indev.h"
#include "bsp_sdspi.h"
 
#include "app_lvgl.h"

#define GUI_GUIDER     1
#if GUI_GUIDER
#include "gui_guider.h"
#include "events_init.h"
#include "custom.h"

/**********************
 *   GLOBAL FUNCTIONS
 **********************/
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
lv_ui guider_ui;
#endif
 
#define TAG  "app_lvgl"

LV_FONT_DECLARE(myChineseFont); 

static lv_obj_t *lv_wifi = NULL;
static lv_obj_t *lv_ble  = NULL;
static lv_obj_t *lv_server  = NULL;

typedef struct {
    lv_obj_t *day;    // 年月日星期
    lv_obj_t *time;    // 时分秒
} sntp_label_t;

EXT_RAM_ATTR static sntp_label_t sntp_label;
 
#define LV_WIN_NUM  6   // 窗口数
static uint8_t lv_win_item = 0;  // 记录当前是在第几个窗口
EXT_RAM_ATTR static lv_obj_t *lv_win[LV_WIN_NUM] = { NULL };  // 窗口

typedef union {
    struct {
        uint8_t id   :   7;   // matrix button id
        uint8_t ok   :   1;   // true: OK?
    };
    uint8_t val;
} btn_activi_t;

static btn_activi_t btn_activi = { .val = 0x00 };  // 按下时按键ID 

//=====================================================================================
//=====================================================================================
static QueueHandle_t xQueue = NULL;     
static lv_event_callback_t lv_event_callback = NULL; 

void lv_event_register_callback(lv_event_callback_t cb)
{
    lv_event_callback = cb;
    xQueue = xQueueCreate(3, sizeof(lv_app_event_t));
}

static void lv_app_event_send(lv_app_event_t app_event)
{
    if (xQueue == NULL) return;
    xQueueSend(xQueue, &app_event, (TickType_t)0);  // 发送队列数据
}

//=====================================================================================
//=====================================================================================
typedef struct {
    uint8_t id;
    lv_obj_t *btn;
    lv_obj_t *msgbox;
    bool start;
} lv_scene_t;

EXT_RAM_ATTR static lv_scene_t lv_scene;

static bool curtain_select = 0;
 
static void msgbox_event_callback(lv_event_t * event)
{
    lv_event_code_t lv_event_code = lv_event_get_code(event);
    lv_obj_t * msgbox = lv_event_get_current_target(event);
 
    if ((lv_event_code == LV_EVENT_VALUE_CHANGED) && (msgbox != NULL)) {
        const char *txt = lv_msgbox_get_active_btn_text(msgbox);
        if (strcmp(txt, "OK") == 0) {
            lv_app_event_t app_event;
            app_event.event = LV_EVENT_VALUE_CHANGED;
            app_event.value[0] = 1;   
            app_event.len = 1; 
            lv_app_event_send(app_event);
        }
        // printf("lv_msgbox = %s\n", txt);
    }  
}

void app_lv_scene_close(void)
{
    if (lv_scene.start) {
        lv_msgbox_close(lv_scene.msgbox);
        lv_btnmatrix_clear_btn_ctrl(lv_scene.btn, lv_scene.id, LV_BTNMATRIX_CTRL_CHECKED);
        lv_scene.start = 0;
    }
} 

void app_lv_scene_create(const char *name)
{
    if (lv_scene.start) return;

    static const char *button_array[] ={"OK", ""};   // "Cancel"
    lv_scene.msgbox = lv_msgbox_create(lv_win[2], "Warning", "Exit scenario mode.", button_array, false);
    lv_obj_t *msgbox = lv_scene.msgbox;
    if (msgbox != NULL) {
        lv_obj_add_event_cb(msgbox, msgbox_event_callback, LV_EVENT_VALUE_CHANGED, NULL);
        lv_obj_align(msgbox, LV_ALIGN_CENTER, 0, 0);

        static lv_style_t msgbox_style;
        lv_style_init(&msgbox_style); // 初始化按钮样式
        lv_style_set_radius(&msgbox_style, 0); // 设置样式圆角弧度
        lv_obj_add_style(msgbox, &msgbox_style, 0);
 
        lv_obj_t * background_obj = lv_obj_get_parent(msgbox);
        if (background_obj != NULL) {
            lv_obj_set_style_bg_opa(background_obj, LV_OPA_80, 0);
            lv_obj_set_style_bg_color(background_obj, lv_palette_lighten(LV_PALETTE_GREY, 1), 0);
        }
        lv_scene.start = 1;
        if (btn_activi.val == 0) {
            if (name[0] == '1') {  // 睡眠模式
                lv_scene.id = 7;
            } else {
                lv_scene.id = 6;
            }
            lv_btnmatrix_set_btn_ctrl(lv_scene.btn, lv_scene.id, LV_BTNMATRIX_CTRL_CHECKED);
        }
    }
}
 
// 矩阵按键事件
static void btnm_event_handler(lv_event_t *e)
{
    if (lv_scene.start) return;
    lv_event_code_t lv_event_code = lv_event_get_code(e); // 获取事件code
    lv_obj_t *btn = lv_event_get_target(e);      // 获取事件对象
    if (btn == NULL)  return;
    uint8_t btn_id = lv_btnmatrix_get_selected_btn(btn); // 获取matrix button id
    if (btn_id < 10) btn_id += 1; // ( +1 防止非0 )
    switch (lv_event_code) {
    case LV_EVENT_PRESSED:  // 按键刚按下
        btn_activi.ok = true;      
        btn_activi.id = btn_id;  // 记录下首次按下的按键ID
        lv_scene.id = btn_id - 1;
        // lv_btnmatrix_set_btn_ctrl(btn, btn_activi.id - 1, LV_BTNMATRIX_CTRL_CHECKED); 
        break;
    case LV_EVENT_PRESSING:  // 持续按着;  过滤乱滑动的情况！
        if (btn_activi.id != btn_id) {   // 如果在持续按着的过程中，滑动到了别的按键那么此次按键无效！
            btn_activi.ok = false;  // 出错了！
        }
        break;
    case LV_EVENT_CLICKED:  // 释放按键
        if (btn_activi.ok  == true) {  // 对比是否是刚按下的那个按键！
            // ESP_LOGI(TAG, "btnm_envent_id: %d", btn_activi.id - 1); 
            lv_btnmatrix_set_btn_ctrl(btn, btn_activi.id - 1, LV_BTNMATRIX_CTRL_CHECKED); 
            lv_app_event_t app_event;
            app_event.event = lv_event_code;
            app_event.value[0] = btn_activi.id - 1;  // 为了从0开始
            app_event.value[1] = curtain_select;
            app_event.len = 2;
            lv_app_event_send(app_event);
        } 
        btn_activi.val = 0x00;  // 清零
        break;
    // case LV_EVENT_RELEASED:  // 触摸完全释放
    //     lv_btnmatrix_clear_btn_ctrl(btn, btn_activi.id - 1, LV_BTNMATRIX_CTRL_CHECKED);
    //     break;
    default:
        break;
    }
    return;
}

typedef struct {
    lv_obj_t *play;
    lv_obj_t *vol;
    lv_obj_t *name_obj;
    bool play_status;
} lv_music_t;
 
static lv_music_t lv_music;
 
void app_music_lvgl_name_text(const char *name)
{ 
    if (lv_music.name_obj == NULL)  return;
    lv_label_set_text_fmt(lv_music.name_obj, "#001080 %s", name + 12);
}

// 音乐播放控制
void app_music_play_ctrl(bool play, audio_ctrl_t ctrl)
{
    lv_music.play_status = play;
    if ( lv_music.play_status ) {
        lv_label_set_text(lv_music.play, LV_SYMBOL_PAUSE);
    } else {
        lv_label_set_text(lv_music.play, LV_SYMBOL_PLAY);
    }
    if (ctrl != AUDIO_CTRL_NONE) {
        audio_music_play_ctrl(ctrl); 
    }
}

void app_music_play_vol(uint8_t vol)
{
    lv_slider_set_value(lv_music.vol, vol, LV_ANIM_OFF);
    audio_music_set_volume(vol); 
}

static void music_event_cb(lv_event_t *e)
{
    char *user_data = (char *)e->user_data;  // 看是哪个按键按下的！
    if (user_data[0] == 'v') {
        if (lv_event_get_code(e) == LV_EVENT_VALUE_CHANGED) {
            uint8_t volume = lv_slider_get_value(lv_event_get_target(e));
            audio_music_set_volume(volume);
            printf("Value: %d\n", volume);
        }
        return; 
    } 

    /**下面是音乐控制 */
    audio_ctrl_t audio_ctrl = 0;
 
    switch (user_data[0]) {
    case '0':
        audio_ctrl = AUDIO_CTRL_LAST;
        lv_music.play_status  = true;
        break;
    case '1': {
        lv_music.play_status  = !lv_music.play_status ;
        if ( lv_music.play_status  ) {
            audio_ctrl = AUDIO_CTRL_RUNNING;
        } else {
            audio_ctrl = AUDIO_CTRL_PAUSED;
        }
        break;
    }
    case '2':
        audio_ctrl = AUDIO_CTRL_NEXT;
        lv_music.play_status  = true;
        break;  
    default:
        break;
    }

    app_music_play_ctrl(lv_music.play_status, audio_ctrl);
}
 
static lv_obj_t *travel_label = NULL;
 
static void curtain_event_cb(lv_event_t *e)
{
    lv_event_code_t lv_event_code = lv_event_get_code(e); // 获取事件code

    if (lv_event_code == LV_EVENT_VALUE_CHANGED) {
        uint8_t value = lv_slider_get_value(lv_event_get_target(e));
        lv_label_set_text_fmt(travel_label, "travel: %d%%", value);
        printf("Value: %d\n", value);
        lv_app_event_t app_event;
        app_event.event = lv_event_code;
        app_event.value[0] = value;   
        app_event.value[1] = curtain_select;   
        app_event.len = 2;
        lv_app_event_send(app_event);
    }
}
extern bool get_sys_config_network(void);
// 单个按键事件
static void btn_event_cb(lv_event_t *e)
{
    if (get_sys_config_network() == false) return;   // 还没配网，不允许切换
    static bool rst_event_status = 0;  // 0: RST按键事件状态
    lv_event_code_t lv_event_code = lv_event_get_code(e); // 获取事件code
    lv_obj_t *btn = lv_event_get_target(e);      // 获取事件对象
    if (btn == NULL)  return;
    switch (lv_event_code) {
    case LV_EVENT_SHORT_CLICKED: { 
        const char *text = NULL;
        lv_obj_t *label = lv_obj_get_child(btn, 0);  // 获取第一个子对象
        const char *user_data = lv_event_get_user_data(e);
        if (user_data != NULL && strcmp(user_data, "curtain") == 0) {  // 窗帘选择
            curtain_select = !curtain_select;
            if (curtain_select) text = "2";
            else text = "1";
        } else {
            rst_event_status = !rst_event_status;
            if (rst_event_status)  text = "TEST"; 
            else text = "RST";
            lv_app_event_t app_event;
            app_event.event = lv_event_code;
            app_event.value[0] = rst_event_status;
            app_event.len = 1;
            lv_app_event_send(app_event);
        }
        lv_label_set_text(label, text);
        // ESP_LOGI(TAG, "LV_EVENT_SHORT_CLICKED");
        break;
    }
    case LV_EVENT_LONG_PRESSED: {
        if ( !rst_event_status ) {
            lv_app_event_t app_event;
            app_event.event = lv_event_code;
            app_event.value[0] = rst_event_status;
            app_event.len = 1;
            lv_app_event_send(app_event);
        }
        // ESP_LOGI(TAG, "LV_EVENT_LONG_PRESSED");
        break;
    }
    case LV_EVENT_LONG_PRESSED_REPEAT: {
        // ESP_LOGI(TAG, "LV_EVENT_LONG_PRESSED_REPEAT");
        break;
    }
    case LV_EVENT_PRESSED: 
        btn_activi.val = 1;  
        break;
    case LV_EVENT_CLICKED:  
        btn_activi.val = 0;
        break;
    default:
        break;
    }
}
 
/** 触摸手势识别，切换界面 */
static void gesture_event_handler(lv_event_t *e)
{
    if (btn_activi.val) return;  // 按着矩阵按键滑动的不执行界面切换，只支持空白处滑动！
    if (get_sys_config_network() == false) return;   // 还没配网，不允许切换
    lv_event_code_t lv_event_code = lv_event_get_code(e); // 获取事件code
    if (lv_event_code == LV_EVENT_GESTURE) {  // 手势识别事件
        lv_indev_wait_release( lv_indev_get_act() );  // 等待触屏释放
        lv_dir_t lv_dir = lv_indev_get_gesture_dir( lv_indev_get_act() ); // 获取方向
        #if 1
        if (lv_win_item == 0) {
            if (lv_dir == LV_DIR_LEFT) {
                lv_obj_add_flag(lv_win[lv_win_item], LV_OBJ_FLAG_HIDDEN);   // 使能隐藏
                lv_win_item += 1; // printf("to LV_DIR_LEFT\n");  
                lv_obj_clear_flag(lv_win[lv_win_item], LV_OBJ_FLAG_HIDDEN); // 显示
            }
        } else if (lv_win_item < LV_WIN_NUM - 1) {
            if (lv_dir == LV_DIR_LEFT) {
                lv_obj_add_flag(lv_win[lv_win_item], LV_OBJ_FLAG_HIDDEN);   // 使能隐藏
                lv_win_item += 1; // printf("to LV_DIR_LEFT\n");  
                lv_obj_clear_flag(lv_win[lv_win_item], LV_OBJ_FLAG_HIDDEN); // 显示
            } else if (lv_dir == LV_DIR_RIGHT) {
                lv_obj_add_flag(lv_win[lv_win_item], LV_OBJ_FLAG_HIDDEN);   // 使能隐藏
                lv_win_item -= 1; // printf("to LV_DIR_RIGHT\n");  
                lv_obj_clear_flag(lv_win[lv_win_item], LV_OBJ_FLAG_HIDDEN); // 显示
            }  
        } else if (lv_dir == LV_DIR_RIGHT) {
            lv_obj_add_flag(lv_win[lv_win_item], LV_OBJ_FLAG_HIDDEN);   // 使能隐藏
            lv_win_item -= 1; // printf("to LV_DIR_RIGHT\n");  
            lv_obj_clear_flag(lv_win[lv_win_item], LV_OBJ_FLAG_HIDDEN); // 显示
        }
        #endif
    } 
}

void main_btn_event_cb(lv_event_t *e)
{
    ESP_LOGI(TAG, "main_btn_event_cb: %s", (char *)e->user_data);
}
 
void lv_win0_init(void)
{
    lv_win[0] = lv_obj_create(lv_scr_act()); // 基于屏幕创建一个和屏幕大小一样的容器
    lv_obj_t *lv_obj = lv_win[0];  // 统一变量名
    lv_obj_set_size(lv_obj, LCD_X_PIXELS, LCD_Y_PIXELS);
    lv_obj_set_style_bg_color(lv_obj, lv_color_hex(0x000000), 0); // 设置背景色

#if 0
    const char *main_img[] = {"device", "bed", "curtain", "music"};
    //===========================================================
    for (uint8_t i = 0; i < 4; i++) {
        char jmg_src[24];
        sprintf(jmg_src, "S:/ICON/%s.bin", main_img[i]);
        lv_obj_t *btn = lv_btn_create(lv_obj);   // 创建Button对象
        lv_obj_set_size(btn, 48, 48);  // 设置对象大小
        lv_obj_set_pos(btn, 20 + i * 60, 20 ); 
        lv_obj_add_event_cb(btn, main_btn_event_cb, LV_EVENT_CLICKED, (char *)main_img[i]); //　添加CLICK事件

        lv_obj_t *img = lv_img_create(btn);
        lv_obj_center(img);   
        lv_img_set_src(img, jmg_src);
    }
#endif    
    //==================================================================
 
#if 0  // 中文显示
    LV_FONT_DECLARE(myChineseFont); 
    lv_obj_t* label1 = lv_label_create(lv_obj);
    lv_label_set_recolor(label1, true);
    lv_obj_set_style_text_font(label1, &myChineseFont, 0);
    lv_obj_set_pos(label1, 20, 20);

    vTaskDelay(200);
    uint8_t mp3_index = 0;
    #define MP3_PATH  MOUNT_POINT"/MP3"    // "/sdcard/MUSIC"
    const char *music_path = fat_open_audio_name(MP3_PATH, mp3_index);
    if (music_path == NULL)  return;
    #ifdef TAG
    ESP_LOGI(TAG, "[ * ] music_name[%d]: %s", mp3_index, music_path);
    #endif
    // lv_label_set_text(label1, "#001080 Hello LVGL, 你好世界!");
    lv_label_set_text_fmt(label1, "#001080 %s", music_path + 12);
#endif

#if 1
    /****** 顶层图标初始化 ******/
    /* Create an icon object to display an icon */
    lv_ble = lv_img_create(lv_obj);
    lv_obj_align(lv_ble, LV_ALIGN_TOP_RIGHT, -56, 0);
    lv_img_set_src(lv_ble, LV_SYMBOL_BLUETOOTH); 

    lv_wifi = lv_label_create(lv_obj);
    lv_obj_align(lv_wifi, LV_ALIGN_TOP_RIGHT, -30, 0);
    lv_label_set_text(lv_wifi, LV_SYMBOL_WIFI);
    // lv_label_set_text(lv_wifi, "\0");

    lv_server = lv_label_create(lv_obj);
    lv_obj_align(lv_server, LV_ALIGN_TOP_RIGHT, -4, 0);
    lv_label_set_text(lv_server, LV_SYMBOL_REFRESH);
  
    LV_FONT_DECLARE(lcd_font32);
    sntp_label.time = lv_label_create(lv_obj);
    lv_label_set_recolor(sntp_label.time, true);
    lv_obj_set_style_text_font(sntp_label.time, &lcd_font32, 0); // set font for default state
    lv_label_set_text(sntp_label.time, "#00FF00 14:53:00");
    lv_obj_align(sntp_label.time, LV_ALIGN_CENTER, 0, -126);   

    sntp_label.day = lv_label_create(lv_obj);
    lv_label_set_recolor(sntp_label.day, true);
    lv_obj_set_style_text_font(sntp_label.day, &lv_font_montserrat_16, 0); 
    lv_label_set_text(sntp_label.day, "2023-03-16 3");
    lv_obj_align(sntp_label.day, LV_ALIGN_CENTER, 0, -100);   
#endif    

#if 1
    //===========================================================
    lv_obj_t *btn = lv_btn_create(lv_obj);   // 创建Button对象
    lv_obj_set_size(btn, 50, 50);  // 设置对象大小
    lv_obj_align(btn, LV_ALIGN_CENTER, 0, +130);
    lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL); //　添加CLICK事件

    lv_obj_t *btn_label = lv_label_create(btn);  // Button对象上创建Label对象
    lv_obj_set_style_text_font(btn_label, &lv_font_montserrat_16, 0); 
    lv_label_set_text(btn_label, "RST"); // 设置显示内容
    lv_obj_center(btn_label);  // 居中显示(这里是在btn的中间)
#endif
 
#if 1 // LV_USE_QRCODE
    /**
     * Create a QR Code
     */
    lv_color_t bg_color = lv_palette_lighten(LV_PALETTE_LIGHT_BLUE, 5);
    lv_color_t fg_color = lv_palette_darken(LV_PALETTE_BLUE, 4);

    lv_obj_t *qr = lv_qrcode_create(lv_obj, 150, fg_color, bg_color);

    /*Set data*/
    uint8_t addr[6];
    esp_read_mac(addr, ESP_MAC_BT);
    char chip_name[16];
    esp_read_chip_name("YiRoot", chip_name);
    char data[32];
    sprintf(data, "%s:"ADDRSTR"", chip_name, ADDR2STR(addr));   
    // const char *data = "https://lvgl.io";
    lv_qrcode_update(qr, data, strlen(data));
    lv_obj_center(qr);

    /*Add a border with bg_color*/
    lv_obj_set_style_border_color(qr, bg_color, 0);
    lv_obj_set_style_border_width(qr, 5, 0);
#endif 

    lv_obj_t *label = lv_label_create(lv_obj);
    lv_label_set_recolor(label, true);
    lv_label_set_text(label, "#001080 Yiree Smart >");
    lv_obj_align(label, LV_ALIGN_BOTTOM_MID, 0, -6); 

}

// music menuconfig
void lv_win1_init(void)
{
    lv_win[1] = lv_obj_create(lv_scr_act()); // 基于屏幕创建一个和屏幕大小一样的容器
    lv_obj_t *lv_obj = lv_win[1];  // 统一变量名
    lv_obj_set_size(lv_obj, LCD_X_PIXELS, LCD_Y_PIXELS);
    lv_obj_set_style_bg_color(lv_obj, lv_color_hex(0xF08000), 0); // 设置背景色
    lv_obj_set_style_bg_grad_color(lv_obj, lv_color_hex(0x808060), 0);
    lv_obj_set_style_bg_grad_dir(lv_obj, LV_GRAD_DIR_VER, 0);

    lv_obj_add_flag(lv_obj, LV_OBJ_FLAG_HIDDEN);   // 隐藏

    //============================================
 
    //===========================================================
    lv_obj_t *btn = lv_btn_create(lv_obj);   // 创建Button对象
    lv_obj_set_size(btn, 60, 60);  // 设置对象大小
    lv_obj_align(btn, LV_ALIGN_CENTER, -100, 0);
    lv_obj_add_event_cb(btn, music_event_cb, LV_EVENT_CLICKED, "0"); //　添加CLICK事件

    lv_obj_t *btn_label = lv_label_create(btn);  // Button对象上创建Label对象
    lv_label_set_text(btn_label, LV_SYMBOL_PREV);
    lv_obj_center(btn_label);  // 居中显示(这里是在btn的中间)
    //==================================================================
    btn = lv_btn_create(lv_obj);   // 创建Button对象
    lv_obj_set_size(btn, 60, 60);  // 设置对象大小
    lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
    lv_obj_add_event_cb(btn, music_event_cb, LV_EVENT_CLICKED, "1"); //　添加CLICK事件

    lv_music.play = lv_label_create(btn);  // Button对象上创建Label对象
    lv_label_set_text(lv_music.play, LV_SYMBOL_PLAY);
    lv_obj_center(lv_music.play);  // 居中显示(这里是在btn的中间)

    //==================================================================
    btn = lv_btn_create(lv_obj);   // 创建Button对象
    lv_obj_set_size(btn, 60, 60);  // 设置对象大小
    lv_obj_align(btn, LV_ALIGN_CENTER, 100, 0);
    lv_obj_add_event_cb(btn, music_event_cb, LV_EVENT_CLICKED, "2"); //　添加CLICK事件

    btn_label = lv_label_create(btn);  // Button对象上创建Label对象
    lv_label_set_text(btn_label, LV_SYMBOL_NEXT);
    lv_obj_center(btn_label);  // 居中显示(这里是在btn的中间)

    //==================================================================
    /*Create a slider*/
    lv_music.vol = lv_slider_create(lv_obj);
    lv_obj_t *slider = lv_music.vol;
    lv_slider_set_range(slider, 0, 100);  // 音量范围 
    lv_slider_set_value(slider, 10, LV_ANIM_OFF);
    lv_obj_align(slider, LV_ALIGN_CENTER, 0, -100);
    lv_obj_add_event_cb(slider, music_event_cb, LV_EVENT_VALUE_CHANGED, "v");
    lv_obj_t *slider_label = lv_label_create(lv_obj);        
    lv_label_set_text(slider_label,"volume:");                
    lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_TOP_LEFT, 0, -5); //设置位置

    //==================================================================
#if 1  // 显示中文音乐名 
    lv_music.name_obj = lv_label_create(lv_obj);
    lv_obj_t *mlabel = lv_music.name_obj;
    lv_label_set_recolor(mlabel, true);
    lv_obj_set_style_text_font(mlabel, &myChineseFont, 0);
    lv_obj_align(mlabel, LV_ALIGN_CENTER, 0, -60);
    // lv_label_set_long_mode(lv_music.name_obj, LV_LABEL_LONG_DOT);
    // lv_obj_set_size(lv_music.name_obj, 200, 100); // 宽度为 100 像素,高度是无效的,随意设置吧
    lv_label_set_text(lv_music.name_obj, "#001080 暂停中...");
#endif

    //========================================================================================
    lv_obj_t *label = lv_label_create(lv_obj);
    lv_label_set_recolor(label, true);
    lv_label_set_text(label, "#001080 < Music Menu >");
    lv_obj_align(label, LV_ALIGN_BOTTOM_MID, 0, -6);
}
 
void lv_win2_init(void)
{
    lv_win[2] = lv_obj_create(lv_scr_act()); // 基于屏幕创建一个和屏幕大小一样的容器
    lv_obj_t *lv_obj = lv_win[2];  // 统一变量名
    lv_obj_set_size(lv_obj, LCD_X_PIXELS, LCD_Y_PIXELS);
    lv_obj_set_style_bg_color(lv_obj, lv_color_hex(0xF06000), 0); // 设置背景色
    lv_obj_set_style_bg_grad_color(lv_obj, lv_color_hex(0x008030), 0);
    lv_obj_set_style_bg_grad_dir(lv_obj, LV_GRAD_DIR_VER, 0);

    lv_obj_add_flag(lv_obj, LV_OBJ_FLAG_HIDDEN);   // 隐藏
 
    //========================================================================
    static const char *btnm_map[] = {
        " ", " ", " ", "\n",  /* "\n" 换行 */
        " ", " ", " ", "\n",  /* "\n" 换行 */
        " ", " ", " ", ""     /* ""   结尾 */
    };  
    // https://blog.csdn.net/mygod2008ok/article/details/123607145
    lv_scene.btn = lv_btnmatrix_create(lv_obj);  // 创建matrix按键对象
    lv_obj_t *win_btnm = lv_scene.btn;
    lv_obj_set_size(win_btnm, 280, 280);  // 设置对象大小
    // lv_obj_set_style_bg_color(win_btnm, lv_color_hex(0x000000), 0);  // 设置矩阵的背景色
    // lv_btnmatrix_set_btn_ctrl_all(win_btnm, LV_BTNMATRIX_CTRL_CLICK_TRIG);
    lv_btnmatrix_set_one_checked(win_btnm, true);  // 使能只能按一个按键
    lv_btnmatrix_set_map(win_btnm, btnm_map);
    lv_obj_align(win_btnm, LV_ALIGN_CENTER, 0, 0); //居中屏幕显示
    lv_obj_add_event_cb(win_btnm, btnm_event_handler, LV_EVENT_ALL, NULL); //　添加CLICK事件
    //============================================
    static lv_style_t style_btn;
    lv_style_init(&style_btn);
    lv_style_set_bg_opa(&style_btn, LV_OPA_0);  // 完全透明
    lv_obj_add_style(win_btnm, &style_btn, LV_STATE_DEFAULT); //设置按键子项的style
    //============================================

    uint8_t img_index = 0;
    const char *bed_img[] = {"read_icon", "tv_icon", "memory_icon", 
                             "music_icon", "sleep_icon", "zero_icon",
                             "wakeup", "sleep", "tv"};
    //===========================================================
    for (uint8_t y = 0; y < 3; y++) {
        for (uint8_t x = 0; x < 3; x++) {
            char jmg_src[24];
            sprintf(jmg_src, "S:/ICON/%s.bin", bed_img[img_index++]);
            lv_obj_t *img = lv_img_create(win_btnm);
            lv_obj_set_pos(img, 13 + 86 * x, 13 + 86 * y);
            lv_img_set_src(img, jmg_src);
        }
    }
 
    //=========================================================================================
    lv_obj_t *label = lv_label_create(lv_obj);
    lv_label_set_recolor(label, true);
    // lv_obj_set_style_text_font(yiree, &lv_font_montserrat_16, 0); 
    lv_label_set_text(label, "#001080 < Bed Motor >");
    lv_obj_align(label, LV_ALIGN_BOTTOM_MID, 0, -6); 
}
 
void lv_win3_init(void)
{
    lv_win[3] = lv_obj_create(lv_scr_act()); // 基于屏幕创建一个和屏幕大小一样的容器
    lv_obj_t *lv_obj = lv_win[3];  // 统一变量名
    lv_obj_set_size(lv_obj, LCD_X_PIXELS, LCD_Y_PIXELS);
    lv_obj_set_style_bg_color(lv_obj, lv_color_hex(0x308000), 0); // 设置背景色
    lv_obj_set_style_bg_grad_color(lv_obj, lv_color_hex(0x008060), 0);
    lv_obj_set_style_bg_grad_dir(lv_obj, LV_GRAD_DIR_VER, 0);

    lv_obj_add_flag(lv_obj, LV_OBJ_FLAG_HIDDEN);   // 隐藏

    static const char *btnm_map[] = {
        " ", " ", " ", ""     /* ""   结尾 */
    };  

    lv_obj_t *win3_btnm = lv_btnmatrix_create(lv_obj);  // 创建matrix按键对象
    lv_obj_set_size(win3_btnm, 280, 280);  // 设置对象大小
    lv_obj_align(win3_btnm, LV_ALIGN_CENTER, 0, 0); //居中屏幕显示
    lv_btnmatrix_set_map(win3_btnm, btnm_map);
    lv_btnmatrix_set_one_checked(win3_btnm, true);  // 使能只能按一个按键
    lv_obj_add_event_cb(win3_btnm, btnm_event_handler, LV_EVENT_ALL, NULL); //　添加CLICK事件
    //============================================
    static lv_style_t style_btn;
    lv_style_init(&style_btn);
    lv_style_set_bg_opa(&style_btn, LV_OPA_0);  // 完全透明
    lv_obj_add_style(win3_btnm, &style_btn, LV_STATE_DEFAULT); //设置按键子项的style
    //============================================
 
    const char *curtain_img[] = {"curtain_on", "curtain_stop", "curtain_off"};
    //===========================================================
    for (uint8_t i = 0; i < 3; i++) {
        char jmg_src[24];
        sprintf(jmg_src, "S:/ICON/%s.bin", curtain_img[i]);
        lv_obj_t *img = lv_img_create(win3_btnm);
        lv_obj_set_pos(img, 13 + 86 * i, 99);
        lv_img_set_src(img, jmg_src);
    }

    //==================================================================
    /*Create a slider*/
    lv_obj_t *slider = lv_slider_create(lv_obj);
    lv_slider_set_range(slider, 0, 100);
    lv_slider_set_value(slider, 100, LV_ANIM_OFF);
    lv_obj_align(slider, LV_ALIGN_TOP_MID, 0, +40);
    lv_obj_add_event_cb(slider, curtain_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
    travel_label = lv_label_create(lv_obj);        
    lv_label_set_text(travel_label, "travel: 100%");                
    lv_obj_align_to(travel_label, slider, LV_ALIGN_OUT_TOP_LEFT, 0, -5); // 设置位置

    //=========================================================================================
    lv_obj_t *btn = lv_btn_create(lv_obj);   // 创建Button对象
    lv_obj_set_size(btn, 50, 50);  // 设置对象大小
    lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -30); 
    lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_SHORT_CLICKED, "curtain"); //　添加CLICK事件

    lv_obj_t *btn_label = lv_label_create(btn);  // Button对象上创建Label对象
    lv_obj_set_style_text_font(btn_label, &lv_font_montserrat_16, 0); 
    lv_label_set_text(btn_label, "1"); // 设置显示内容
    lv_obj_center(btn_label);  // 居中显示(这里是在btn的中间)

    //=========================================================================================
    lv_obj_t *label = lv_label_create(lv_obj);
    lv_label_set_recolor(label, true);
    lv_label_set_text(label, "#001080 < Curtian Motor >");
    lv_obj_align(label, LV_ALIGN_BOTTOM_MID, 0, -6); 
}

void lv_win4_init(void)
{
    lv_win[4] = lv_obj_create(lv_scr_act()); // 基于屏幕创建一个和屏幕大小一样的容器
    lv_obj_t *lv_obj = lv_win[4];  // 统一变量名
    lv_obj_set_size(lv_obj, LCD_X_PIXELS, LCD_Y_PIXELS);
    lv_obj_set_style_bg_color(lv_obj, lv_color_hex(0x608080), 0); // 设置背景色
    lv_obj_set_style_bg_grad_color(lv_obj, lv_color_hex(0x308030), 0);
    lv_obj_set_style_bg_grad_dir(lv_obj, LV_GRAD_DIR_VER, 0);

    lv_obj_add_flag(lv_obj, LV_OBJ_FLAG_HIDDEN);   // 隐藏

    //============================================
    static const char *btnm_map[] = {
        " ", " ", "\n",  /* "\n" 换行 */
        " ", " ", "\n",  /* "\n" 换行 */
        "OFF", ""   /* ""   结尾 */
    };  

    lv_obj_t *win_btnm = lv_btnmatrix_create(lv_obj);  // 创建matrix按键对象
    lv_obj_set_size(win_btnm, 280, 280);  // 设置对象大小
    lv_obj_align(win_btnm, LV_ALIGN_CENTER, 0, 0); //居中屏幕显示
    lv_btnmatrix_set_map(win_btnm, btnm_map);
    lv_btnmatrix_set_one_checked(win_btnm, true);  // 使能只能按一个按键
    lv_obj_add_event_cb(win_btnm, btnm_event_handler, LV_EVENT_ALL, NULL); //　添加CLICK事件
    //============================================
    static lv_style_t style_btn;
    lv_style_init(&style_btn);
    lv_style_set_bg_opa(&style_btn, LV_OPA_0);  // 完全透明
    lv_obj_add_style(win_btnm, &style_btn, LV_STATE_DEFAULT); //设置按键子项的style
    //============================================

    //===========================================================
    uint8_t index = 0;
    for (uint8_t y = 0; y < 2; y++) {
        for (uint8_t x = 0; x < 2; x++) {
            char jmg_src[24];
            sprintf(jmg_src, "S:/ICON/suman-%d.bin", index++);
            lv_obj_t *img = lv_img_create(win_btnm);
            lv_obj_set_pos(img, 40 + 120 * x, 13 + 86 * y);
            lv_img_set_src(img, jmg_src);
        }
    }

    //=========================================================================================
    lv_obj_t *label = lv_label_create(lv_obj);
    lv_label_set_recolor(label, true);
    lv_label_set_text(label, "#001080 < Sumanbo Menu");
    lv_obj_align(label, LV_ALIGN_BOTTOM_MID, 0, -6); 
}

//==================================================================================================
//==================================================================================================
void lv_time_handle(struct tm *sntp)
{
#if 1
    static struct tm time = { 0 };
    if (sntp_label.time == NULL) return;
    // struct tm *sntp = sntp_get_time();
    if (time.tm_sec != sntp->tm_sec) {
        time.tm_hour = sntp->tm_hour;
        time.tm_min  = sntp->tm_min;
        time.tm_sec  = sntp->tm_sec;
        lv_label_set_text_fmt(sntp_label.time, "#FFFF00 %02d:%02d:%02d", sntp->tm_hour, sntp->tm_min, sntp->tm_sec);
    }
    if (time.tm_year != sntp->tm_year || time.tm_mday != sntp->tm_mday) {  
        time.tm_year = sntp->tm_year;
        time.tm_mon  = sntp->tm_mon;
        time.tm_mday = sntp->tm_mday;
        time.tm_wday = sntp->tm_wday;
        lv_label_set_text_fmt(sntp_label.day,  "%d-%d-%d #F00090  %d", sntp->tm_year, sntp->tm_mon, sntp->tm_mday, sntp->tm_wday + 1);
    }
#endif
}
 
void lv_wifi_icon_switch(bool onoff)
{
    const char *text = NULL; 
    if (onoff) {
        text = LV_SYMBOL_WIFI;
    } else {
        text = "\0";
    }
    lv_label_set_text(lv_wifi, text);
}

void lv_ble_icon_switch(bool onoff)
{
    const char *text = NULL; 
    if (onoff) {
        text = LV_SYMBOL_BLUETOOTH;
    } else {
        text = "\0";
    }
    lv_label_set_text(lv_ble, text);
}

void lv_server_icon_switch(bool onoff)
{
    const char *text = NULL; 
    if (onoff) {
        text = LV_SYMBOL_REFRESH;
    } else {
        text = "\0";
    }
    lv_label_set_text(lv_server, text);
}

 
#if GUI_GUIDER
 
typedef struct {
    bool     onoff; 
    uint16_t hue;
    uint8_t  value;
    uint8_t  cool;
    uint8_t  warm;
} gui_ledc_t;   

static gui_ledc_t ledc = {
    .onoff = 0,
    .hue = 0,
    .value = 50,
    .cool = 50,
    .warm = 50,
};

void app_lv_ledc_set_onoff(bool onoff)
{
    ledc.onoff = onoff;
    if (ledc.onoff) {
        lv_led_on(guider_ui.screen_led_1);
    } else {
        lv_led_off(guider_ui.screen_led_1);
    }
}

void app_lv_ledc_set_cool(uint8_t value)
{
   lv_slider_set_value(guider_ui.screen_slider_1, value, LV_ANIM_OFF); 
} 

void app_lv_ledc_set_warm(uint8_t value)
{
   lv_slider_set_value(guider_ui.screen_slider_2, value, LV_ANIM_OFF); 
} 

void app_lv_ledc_set_value(uint8_t value)
{
   lv_slider_set_value(guider_ui.screen_slider_3, value, LV_ANIM_OFF); 
} 


void app_lv_ledc_set_hsv(uint16_t h, uint8_t s, uint8_t v)
{
    ledc.hue = h;
    lv_color_hsv_t hsv;
    hsv.h = h;
    hsv.s = s;
    hsv.v = v;
    lv_colorwheel_set_hsv(guider_ui.screen_cpicker_1, hsv);
    lv_led_set_color(guider_ui.screen_led_1, lv_colorwheel_get_rgb(guider_ui.screen_cpicker_1));
}

static void gui_btn_event_cb(lv_event_t *e)
{
    lv_event_code_t lv_event_code = lv_event_get_code(e); // 获取事件code
    if (lv_event_code == LV_EVENT_CLICKED) {
        ledc.onoff = !ledc.onoff;
        app_lv_ledc_set_onoff(ledc.onoff);
    }
    //===============================================
    lv_app_event_t app_event;
    app_event.event = 'O';
    app_event.value[0] = ledc.onoff;   
    app_event.len = 1; 
    lv_app_event_send(app_event);
}


static void gui_cpicker_event_cb(lv_event_t *e)
{
    lv_color_hsv_t hsv = lv_colorwheel_get_hsv(guider_ui.screen_cpicker_1);
    if (ledc.hue == hsv.h) return;
    lv_led_set_color(guider_ui.screen_led_1, lv_colorwheel_get_rgb(guider_ui.screen_cpicker_1));
    // ESP_LOGI(TAG, "hsv = %d %d %d", hsv.h, hsv.s, hsv.v);
    if (ledc.onoff == false)  return;
    ledc.hue = 360 - hsv.h;
    //===============================================
    lv_app_event_t app_event;
    app_event.event = 'H';
    app_event.value[0] = ledc.hue >> 8;
    app_event.value[1] = ledc.hue & 0xFF;
    app_event.value[2] = 100;
    app_event.value[3] = ledc.value;
    app_event.value[4] = 3;
    app_event.len = 5;
    lv_app_event_send(app_event);
}
 
static void gui_slider_event_cb(lv_event_t *e)
{
    char *user_data = (char *)e->user_data;  // 看是哪个按键按下的！
    lv_app_event_t app_event;
    if (lv_event_get_code(e) == LV_EVENT_VALUE_CHANGED) {
        uint8_t value = lv_slider_get_value(lv_event_get_target(e));
        switch (user_data[0]) {
        case 'C':
            if (ledc.cool == value) return;
            ledc.cool = value;
            app_event.value[0] = value;
            app_event.value[1] = 3;   
            app_event.len = 2; 
            break;
        case 'W':
            if (ledc.warm == value) return;
            ledc.warm = value;
            app_event.value[0] = value;
            app_event.value[1] = 3;   
            app_event.len = 2; 
            break;
        case 'V':
            if (ledc.value == value) return;
            ledc.value = value;
            app_event.value[0] = ledc.hue >> 8;
            app_event.value[1] = ledc.hue & 0xFF;
            app_event.value[2] = 100;
            app_event.value[3] = ledc.value;
            app_event.value[4] = 3;
            app_event.len = 5;
            break;
        default:
            break;
        }

        // printf("%s->Value: %d\n", user_data, value);
        //===============================================
        if (ledc.onoff == false)  return;
        app_event.event = user_data[0];
        lv_app_event_send(app_event);
    }
}

static void guider_init(void)
{
    /*Create a GUI-Guider app */
    setup_ui(&guider_ui);
    events_init(&guider_ui);
    custom_init(&guider_ui);
    lv_obj_add_event_cb(guider_ui.screen_imgbtn_1, gui_btn_event_cb, LV_EVENT_CLICKED, NULL); //　添加CLICK事件
    lv_obj_add_event_cb(guider_ui.screen_cpicker_1, gui_cpicker_event_cb, LV_EVENT_GET_SELF_SIZE, NULL); 
    lv_obj_add_event_cb(guider_ui.screen_slider_1, gui_slider_event_cb, LV_EVENT_VALUE_CHANGED, "C");
    lv_obj_add_event_cb(guider_ui.screen_slider_2, gui_slider_event_cb, LV_EVENT_VALUE_CHANGED, "W");
    lv_obj_add_event_cb(guider_ui.screen_slider_3, gui_slider_event_cb, LV_EVENT_VALUE_CHANGED, "V");
    lv_win[5] = guider_ui.screen;
    if (guider_ui.screen_label_5 != NULL) {
        lv_obj_set_style_text_font(guider_ui.screen_label_5, &myChineseFont, LV_PART_MAIN|LV_STATE_DEFAULT);
    }
}


#endif 
//==================================================================================================
//==================================================================================================
// LVGL API实例：
// https://deepinout.com/lvgl-tutorials/lvgl-widgets/lvgl-lv_cpicker.html   
 
static void lv_hal_init(void)
{
    lv_port_disp_init();  
    lv_port_indev_init(); 
    lv_fs_fatfs_init(); 

    lv_obj_add_event_cb(lv_scr_act(), gesture_event_handler, LV_EVENT_GESTURE, NULL);  // 增加触摸手势识别事件

    // // 用户GUI绘制初始化
    // lv_win0_init();
    // lv_win1_init();
    // lv_win2_init();
    // lv_win3_init();
    // lv_win4_init();
    // #if GUI_GUIDER
    // guider_init();
    // #endif
    // lcd_bk_set_brightness(50);
}

 
void lv_gui_init(void *pvParameter) 
{
#if 1 // 开机启动界面   
    lv_obj_t *lv_boot = lv_obj_create(lv_scr_act());
    lv_obj_set_size(lv_boot, LCD_X_PIXELS, LCD_Y_PIXELS);
    lv_obj_set_style_bg_color(lv_boot, lv_color_hex(0x000000), 0); // 设置背景色

    lv_obj_t *img = lv_img_create(lv_boot);
    lv_img_set_src(img, "S:/ICON/yiree_log8.bin"); 
    lv_obj_align(img, LV_ALIGN_CENTER, 0, 0);
    #if 0
    lv_obj_t *label = lv_label_create(lv_boot);
    lv_label_set_recolor(label, true);
    // lv_obj_set_style_text_font(label, &myChineseFont, 0);
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 80);
    lv_label_set_text(label, "#e07006 谊瑞健康家居");
    #else
    lv_obj_t *label = lv_label_create(lv_boot);
    lv_label_set_recolor(label, true);
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 80);
    lv_label_set_text(label, "#e07006 Welcome Yiree Health Home");
    #endif
#endif
    audio_music_play_audio(VOICE_COMM_POWER_ON);  // 开机声
    // 需要等界面刷新完后才能打开背光灯
    uint8_t lcd_backlight = 0;  // 背光灯
    while (true) { 
        vTaskDelay(200);
        lcd_bk_set_brightness(++lcd_backlight); // 打开背光
        if (lcd_backlight >= CONFIG_DEFAULT_BACKLIGHT) { 
            #if 1
            lv_obj_clean(lv_boot);  // 清屏
            lv_obj_del(lv_boot);
            #endif
            break;
        }
    }

    // 用户GUI绘制初始化
    lv_win0_init();
    lv_win1_init();
    lv_win2_init();
    lv_win3_init();
    lv_win4_init();
    #if GUI_GUIDER
    guider_init();
    #endif

    // vTaskDelete(NULL);
}

static void lv_gui_task(void *pvParameter) 
{
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xPeriod = pdMS_TO_TICKS(100);
    lv_app_event_t app_event;
    
    while (true) {
 
        if (xQueue != NULL && xQueueReceive(xQueue, &app_event, 0) == pdTRUE) {  // portMAX_DELAY  
            app_event.windows = lv_win_item;
            lv_event_callback(app_event);
        }
 
        // raise the task priority of LVGL and/or reduce the handler period can improve the performance
        vTaskDelayUntil(&xLastWakeTime, xPeriod);
        // The task running lv_timer_handler should have lower priority than that running `lv_tick_inc`
        lv_timer_handler();
    }
}
 
void app_lvgl_init(void)
{
    bsp_sdspi_init();   // 初始化SD-SPI接口

    lv_hal_init();
 
    /* If you want to use a task to create the graphic, you NEED to create a Pinned task
     * Otherwise there can be problem such as memory corruption and so on.
     * NOTE: When not using Wi-Fi nor Bluetooth you can pin the lv_gui_task to core 0 */
    xTaskCreatePinnedToCore(lv_gui_task, "lv_gui_task", 4 * 1024, NULL, 3, NULL, APP_CPU_NUM);

    // xTaskCreatePinnedToCore(lv_gui_init, "lv_gui_init", 4 * 1024, NULL, 4, NULL, APP_CPU_NUM);
    lv_gui_init(NULL);  // LVGL GUI 初始化
}
#if 0
// https://lvgl.io/tools/imageconverter
// http://doc.smartpanle.com/_static/espboard/ESP32S3/ZX3D50CE02S-USRC-4832/ZX3D50CE02S-USRC-4832.pdf

3、LVGL笔记(5):文件系统编译出错[lv_fs_fatfs.c:12:10: fatal error: ff.h: No such file or directory]
    https://blog.csdn.net/hwd00001/article/details/127320616
    DIR 全部贴换为 FF_DIR
#endif